﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
#if !MONODROID
using NLog;
#endif

namespace Devsoft.Utilities
{
    public class AsyncResult<T>
    {
        public AsyncResult()
        {
            ResetEvent = new AutoResetEvent(false);
            ResetEvent.Reset();
            Stopwatch = new Stopwatch();
        }
        public T Result { get; set; }
        public AutoResetEvent ResetEvent { get; set; }
        public Exception Exception { get; set; }
        public Stopwatch Stopwatch { get; set; }

        public object Tag { get; set; }
    }

    public class TaskHelper
    {
        public static void CallWithTimeout(Action action, int timeoutMilliseconds)
        {
            CallWithTimeout(() =>
                {
                    action();
                    return true;
                }, timeoutMilliseconds);
        }

        public static T CallWithTimeout<T>(Func<T> func, int timeoutMilliseconds)
        {
            Thread threadToKill = null;
            T funcResult = default (T);
            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                funcResult = func();
            };

            IAsyncResult result = wrappedAction.BeginInvoke(null, null);
            if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
            {
                wrappedAction.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                throw new TimeoutException();
            }
            return funcResult;
        }
        public static void Try(Action action, int numTrys = 3, bool throwErrorAble = true, int errorDelay = 0)
        {
            TryWithErrorHandler(action, numTrys, throwErrorAble, (e) => Thread.Sleep(errorDelay));
        }

        public static T Try<T>(Func<T> action, int numTrys = 3,  Action<Exception> errorhandler = null)
        {
            return TryWithErrorHandler(action, numTrys, errorhandler);
        }

        public static T TryOrDefault<T>(Func<T> action, int numTrys = 3, Action<Exception> errorhandler = null,T defVal = default (T))
        {
            try
            {
                return TryWithErrorHandler(action, numTrys, errorhandler);
            }
            catch (Exception)
            {
                return defVal;
            }
        }
        public static T TryWithErrorHandler<T>(Func<T> action, int numTrys = 3,  Action<Exception> errorhandler = null)
        {
            Exception lastException = null;
            int times = 0;
            while (times < numTrys)
            {
                times++;
                try
                {
                    return action();
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    logger.Error(ex);
                    if (errorhandler != null)
                    {
                        errorhandler(ex);
                    }
                }
            }
            throw lastException;
        }

        public static void TryWithErrorHandler(Action action, int numTrys = 3, bool throwErrorAble = true, Action<Exception> errorhandler = null)
        {
            Exception lastException = null;
            int times = 0;
            while (times < numTrys)
            {
                times++;
                try
                {
                    action();
                    return;
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    logger.Error(ex);
                    if (errorhandler != null)
                    {
                        errorhandler(ex);
                    }
                }
            }
            if (throwErrorAble)
            {
                throw lastException;
            }
        }

        public static void TryActionAsync(Action action, int numTrys = 3, bool throwErrorAble = true, int errorDelay = 0)
        {
            new Thread(() => Try(action, numTrys, throwErrorAble, errorDelay)).Start();
        }

		public static void TryActionAsyncWithErrorHandler(Action action, int numTrys = 3, bool throwErrorAble = true, Action<Exception> errorHandler = null)
		{
			new Thread(() => TryWithErrorHandler(action, numTrys, throwErrorAble, errorHandler)).Start();
		}

        public static void DoActionAsync(Action action)
        {
            new Thread(new ThreadStart(action)).Start();
        }

        public static AsyncResult<T> StartTask<T>(Func<T> func, bool throwAble = false, int numTrys = 1, int errorDelay = 0,object tag = null)
        {
            var result = new AsyncResult<T>();
            new Thread(() =>
            {
                for (int i = 0; i < numTrys; i++)
                {
                    try
                    {
                        result.Exception = null;
                        result.Tag = tag;
                        result.Stopwatch.Restart();
                        result.Result = func();
                        result.Stopwatch.Stop();
                        result.ResetEvent.Set();
                        
                        return;
                    }
                    catch (Exception e)
                    {
                        logger.ErrorException(e);
                        result.Exception = e;
                        Thread.Sleep(errorDelay);
                    }
                }
                result.ResetEvent.Set();
                if (throwAble && result.Exception != null)
                {
                    throw result.Exception;
                }
            }).Start();
            return result;
        }

#if MONODROID
        private static Logger logger = new Logger();
#else
        private static Logger logger = LogManager.GetCurrentClassLogger();
#endif
    }
#if MONODROID
    internal class Logger
    {
        public void ErrorException(Exception exception)
        {

        }

        public void Error(Exception exception)
        {

        }
    }
#endif
}
